home *** CD-ROM | disk | FTP | other *** search
-
- /**********************( GREGORIAN CALENDAR MODULE )**************************
-
-
- Routines for converting between Gregorian Calendar dates and Julian Days, for
- validating date input, for date arithmetic, and for learning day of the week
- and of the year.
-
- (c) 1991, 1993 by Bob Twilling.
- C Users Journal readers may use this code for any purpose.
-
-
-
- /-------------------( Export to header file "JD2GREG.H" )--------------------*/
-
- typedef struct { short mo;
- short dy;
- short yr; } MDY;
-
- extern MDY * jd2greg( long /*jd*/, MDY * /*date*/ );
- extern long greg2jd( short /*month*/, short /*day*/ , short /*year*/ );
- extern long ValidateDate(short /*month*/, short /*day*/ , short /*year*/ );
- extern long DDays( MDY * /*date1*/, MDY * /*date2*/ );
- extern MDY * DatePlus( MDY * /*date*/ , long /*ddays*/, MDY * /*newdt*/ );
- extern int DayOfWeek( short /*month*/, short /*day*/ , short /*year*/ ,
- char * /*name*/ );
- extern int DayOfYear( short /*month*/, short /*day*/ , short /*year*/ );
-
-
-
- /*=======================( MONTH TO DAY-OF-YEAR )===========================/
-
- Given a month, calc day of year up until. This is a macro used by the next
- two functions, see discussion below for the logic. Assumes rectified years
- (March is month 1). Can change the #if to 0 to save 24 bytes of near heap
- at a slight speed cost.
-
- /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
- #if 1
- static short m2doy[] =
- { 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337 };
- #define M2DOY(mo) (m2doy[mo-1])
- #else
- #define M2DOY(mo) ((((mo) * 979) >> 5) - 30)
- #endif
-
-
-
- /*=========================( GREGORIAN TO JD )==============================/
-
- Given a month, day, and year, returns a long integer -- the Julian Day number
- at noon of that date.
-
- ALGORITHM:
-
- 1> Rectify the date so that the year begins on March 1. This simplifies
- calculations by putting oddball leapdays last.
-
- 2> Now calculate the day of year, e.g. Oct 28th is day number 242, Jan 3rd
- is day number 309 (of the previous year! See above.) A lookup table is
- the fastest way to account for the varied number of days in a month, at
- a cost of 24 bytes of near heap space. Or we can use the formula:
-
- doy = ((mo * 979) / 32) - 30 + dy;
-
- March is the first month, remember. There's no theory behind these magic
- numbers -- a lot of other ones work too, but I like my 32 (a nice round
- number). 979/32, or 367/12, or 30.6, all equal about the average days
- per month, ignoring February. The magic of integer arithmetic, and the
- lucky fact that our 30-day months are well distributed, does the rest.
-
- 3> Compute the number of days up to the beginning of the year using the
- simple Gregorian formula:
-
- yrdays = (365 * yr) + (yr / 4) - (yr / 100) + (yr / 400);
-
- There are some nice round numbers in this formula too, begging to be
- replaced by shifts.
-
- 4> Add the result of the last two steps. Then add a constant for an origin
- transform to the Julian Day system.
-
- /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
- extern long greg2jd( short mo, short dy, short yr ) {
-
- short lp;
-
- if ((mo -= 2) <= 0) { mo += 12; yr--; } // 1> Roll-under the date
- dy += M2DOY(mo); // 2> Calc day of year
- lp = yr >> 2; // 3> Add and subtract leap days
- dy += lp;
- lp /= 25;
- dy -= lp;
- dy += lp >> 2;
- return (long)yr * 365 + dy + 1721119; } // 4> One div, one mul!
-
-
-
- /*=========================( JD TO GREGORIAN )==============================/
-
- Given a long Julian Day and a pointer to a month-day-year structure, fills in
- that structure with the calendar date and returns the same pointer to it.
-
- ALGORITHM:
-
- 1> Transform the origin of the Julian Day to the start of some 400-year
- period. Because we are assuming a JD valid in the Gregorian Calendar we
- subtract 2305507, so that day 1 is 1-Mar-1600. As the routine is currently
- written this gives an algorithm valid from 1200 AD.
-
- 2> Subtract or divide out 400-year (146097 day) blocks, then 100-year (36524
- day) blocks, noting the number of blocks removed. Since 16-bit machines
- and/or compilers do long division like paint dries, we use successive
- subtraction -- sort of. Actually, we subtract too far, then add back, for
- a slight speed gain. When we migrate to 32-bits, rewrite this part for
- speed and comprehensibility.
-
- 3> Divide out 4-year (1461 day) and 1-year (365 day) blocks. Calculate the
- calendar year from the number and size of blocks removed; the remainder is
- the day of the year. If the Julian Day represented a Feb 29, the algorithm
- fails here because 1461/365 equals more than four. Check and adjust for
- this special case.
-
- 4> Calculate the month of the year from the magic-number formula:
-
- mo = ((doy + 30) * 32) / 979;
-
- Surprisingly, this is faster on a 386 than scanning a twelve member table.
- Compute the day of the month by subtracting the days up until that month
- using either the table or the formula declared in M2DOY() above.
-
- 5> Roll over the month and year if we found a date in January or February.
-
- 6> Be careful when changing this code: 146097/36524 and 1461/365 both equal
- 4, which blows up the algorithm on JD's representing Feb 29's. As written,
- we avoid the first by a tricky origin shift, the second by explicitly
- limiting 1461/365 to a max of three.
-
- /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
- #include <stdlib.h> //for div()
- /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
- extern MDY * jd2greg( long jd, MDY * date ) {
-
- unsigned short x, y, d;
- div_t sd;
-
- y = 1600; jd -= 2305507; // 1> new origin 0 = 2/29/1600
- while (jd > 0) { y += 400; jd -= 146097; } // 2> lop off 400-yr blocks
- do { y -= 100; } while ((jd += 36524) < 0); // and add back 100-yr ones
- d = (unsigned short)jd; // 3> within 16-bit range now
- x = d / 1461; // pity ain't no ANSI udiv()
- y += x << 2; // note 4-yr blocks
- d -= x * 1461; // and lop them off
- sd = div( d, 365); // does % and / in same op
- date->yr = y + sd.quot; // got years, provisionally
- d = ++sd.rem; // 4> day-of-year (base 1)
- if (sd.quot == 4) { date->yr--; d = 366; } // case we hit a leap-year
- x = ((d + 30) << 5) / 979; // x = month, March is 1
- date->dy = d - M2DOY(x); // got day of month
- if ((x += 2) > 12) { date->yr++; x -= 12; } // 5> roll around Jan and Feb
- date->mo = x; // got month
- return(date); }
-
-
-
- /*==========================( VALIDATE DATE )===============================/
-
- Given a month, day, and year, returns a positive long integer representing the
- corresponding Julian Day at 12h UTC, unless the passed date is invalid in the
- Gregorian Calendar, in which case returns zero. This somewhat-boolean retval
- will save the user another call to greg2jd() if the date does prove valid.
-
- ALGORITHM:
-
- 1> Convert the passed Gregorian date into its Julian Day.
-
- 2> Check that the date is not earlier than 15 Oct 1582, the first day
- Gregory's calendar was in use anywhere. An earlier date would indicate
- that the caller should have used Julian Calendar (Old Style) conversion
- routines.
-
- 3> Check that the date is not later than 28 Feb 4000. On somewhat shaky
- ground here. If our planet's orbital speed doesn't change much, Greg's
- calendar will lose a day every 3300 years. I read somewhere of a proposal
- for a Revised Gregorian Calendar which drops the leap year in 4000 AD and
- every four thousand years thereafter. But I don't know if the proposal has
- been widely accepted, don't know what's going to happen culturally or
- astronomically over two millennia, and don't really care since this
- module's goal is to implement only Gregory's original system.
-
- Just to be safe, I have provisionally and unilaterally declared 4000 AD
- to be the end of the Gregorian Calendar. When the time comes, make sure
- either to remove this restriction or to rewrite the formulae.
-
- 4> If the Julian Day passes the boundary conditions, convert it back to a
- calendar date and check that that date equals the caller's. This shows up
- mistakes like 31 Nov 1991, or 3.15.1992 (when the caller was expecting
- Canadian-style input).
-
- /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
- extern long ValidateDate( short mo, short dy, short yr) {
-
- MDY date;
- long jd = greg2jd( mo, dy, yr);
- if ((jd >= 2299161) && (jd <= 3182088)) {
- jd2greg(jd, &date);
- if (date.dy == dy && date.mo == mo && date.yr == yr) {
- return jd; } } //suceeded
- return 0; } //failed
-
-
-
- /*=========================( DIFFERENCE IN DAYS )============================
-
- Given two pointers to month-day-year structures, returns the positive or
- negative difference between date2 and date1. Value is positive if the second
- date is later than the first, in keeping with the HP-41 function even though
- this seems to violate RPN logic. No date validation is done, and the
- subtraction could fail for dates outside the original Gregorian range. Could
- easily inline this function.
-
- /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
- extern long DDays( MDY * date1, MDY * date2) {
-
- return greg2jd(date2->mo, date2->dy, date2->yr)
- - greg2jd(date1->mo, date1->dy, date1->yr); }
-
-
-
- /*===========================( DATE ADDITION )===============================
-
- Given two pointers to month-day-year structures and a (positive or negative)
- long integer, adds that integer number of days to the first MDY and fills in
- the second with the answer. Returns the pointer to the second structure.
- Another good candidate for inlining.
-
- /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
- extern MDY * DatePlus( MDY * date, long dys, MDY * newdate) {
-
- return jd2greg((greg2jd( date->mo, date->dy, date->yr) + dys), newdate ); }
-
-
-
- /*============================( DAY OF WEEK )===============================/
-
- Given month-day-year, returns an integer representing the day of the week.
- Zero is Monday. Caller can also get the weekday name by passing a pointer to
- a string workspace at least 7 characters long. Most such callers will want to
- either abbreviate the string to 3 characters, or add a "day" onto it using
- strcat() or as part of a printf() format. Callers that do not want the string
- must pass a NULL pointer. Algorithm is simply (JD mod 7).
-
- /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
- #include <string.h> //for strcpy()
- /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
- int DayOfWeek( short mo, short dy, short yr, char * strg) {
-
- static char * dnm[] = { "Mon","Tues","Wednes","Thurs","Fri","Satur","Sun" };
- int d = (short)(greg2jd(mo,dy,yr) % 7);
- if (strg) strcpy(strg, dnm[d]);
- return d; }
-
-
-
- /*============================( DAY OF YEAR )===============================/
-
- Given a month-day-year, returns the day of that year. Jan first is 1. Handy
- for numbering invoices, etc. An invalid Gregorian date returns 0. The
- routine is correct for Gregory's short year 1582, although we could probably
- save the code -- the result will usually be meaningless for historical
- periods, when the year began in March. Algorithm is simple JD subtraction.
-
- /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
- int DayOfYear( short mo, short dy, short yr) {
-
- long jd = ValidateDate( mo, dy, yr);
- if (jd) {
- if (yr != 1582) {
- return (int)(jd - greg2jd( 1, 1, yr)) + 1; }
- else { //10 days missing from 1582
- return (int)(jd - 2299161 + 278); } }
- else return 0; }
-
-
-
- /*=============================( CAUTIONS )=================================/
-
- 1> All callers, be aware that some of these routines work only when passed a
- valid Julian Day. As a matter of course, validate your calendar dates.
-
- 2> Astronomy callers, be aware....
-
- The Julian Day passed to and from these routines is at 12h UTC. So
- ephemerides should use this formula to calculate the proper fractional
- Julian Day.:
- JD = (double)greg2jd() - 0.5 + (UTC / 24.0);
-
- 3> Non-astronomy users, you can safely assume that Local Time equals UTC only
- so long as you are not crossing different time zones.
-
- 4> Geneology and History callers, be aware for two reasons....
-
- High school history may have taught us that Europeans changed to the
- Gregorian Calendar on 15 Oct 1582. 'Taint so. Only the Papal States
- changed on that date. Even other Catholic countries took a up to a couple
- years to switch, after putting down their rent riots. (What tenant farmer
- wants to pay full price for a 20 day month?) And, on the principle that a
- good idea from my enemy is a bad idea, Protestant countries took a couple
- hundred years to change over, Orthodox ones even longer. Washington was
- born on 11 Feb, he thought, until England switched in 1752 to give us our
- Feb 22 holiday. Russia didn't switch until the Revolution (the 1917, not
- this one).
-
- Reason two: New Years Day wasn't always the first of January. Again,
- Christendom switched from a date, usually in March, to Jan 1 at various,
- seemingly whimsical, times.
-
- The upshot? That date -- in the back of an old Bible, on the Margraves'
- and Metropolitans' proclamations -- may or may not be in Gregory's system.
- You'll need to know the calendar used during your period of study better
- than this program does.
-
-
-
- /===============================( HISTORY )=================================/
-
- Feb 1582: Pope Gregory XIII, acting on recommendation of a scientific
- commission, reforms Julius Caesar's calendar to drop leap days
- on years divisible by 100 unless also divisible by 400. He
- decrees next October will drop 10 days to eliminate accumulated
- error so that Easter gets back on track.
-
- 1582: Joe Scaliger, perhaps inspired by the Mayans, invents a "long
- count" calendar as a standard of comparison between different
- chronological systems. His epoch begins 4713 BC and lasts 7980
- years. He writes routines for both the Julian and Gregorian
- calendars. He names his epoch "Julian Days" after his father-in-
- law (or father or uncle, sources vary), to the eternal confusion
- of students' who imagine it has something to do with the Caesar.
- My sources don't say whether Joe is a member of the papal
- commission or whether it is news reports of their findings that
- inspires him to program his new system.
-
- 1898: Sam Newcomb publishes Julian Day formulae for the "American
- Ephemeris" and "Nautical Almanac." Astronomers are still using
- Joe's epoch because it handily predates all recorded (even
- Chinese and African) observations.
-
- 1979: Jean Meeus publishes "Astronomical Formulae for Calculators" in
- Belgium. A French edition follows in 1980, an American one in
- 1985. The calendar routines in his books are adopted by many
- programmers to replace previous buggy ones. I adopt them in
- 1989. Still, after 400 years, Julian Days are the basis for
- time calculation. Nice job, Joe.
-
- Aug 1991: I write a program in which Meeus' calendar routines are the only
- ones using floating-point math. And they are pulling in a whole
- 16K emulation library. I rewrite to use long integers instead of
- floats. And I drop Julian Calendar conversions -- they will be
- in a separate module should I ever need them.
-
- 9 Feb 1993: I squeeze a little more speed from the integer routines, add
- excessive comments, and add a 4000 AD validation check.
-
-
- /===================================================================ENDIT====*/
-